﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Diagnostics;
using System.Data.SqlClient;
using System.Windows.Forms;
using System.Windows.Threading;
using System.Windows.Media.Imaging;
using System.Windows.Media;
using System.Drawing;

namespace Player
{
    public abstract class WorkerThread<ProgressChangedEventArgs>
        where ProgressChangedEventArgs : EventArgs
    {
        Thread m_thread;
        Dispatcher m_dispatcher;

        public event EventHandler<ProgressChangedEventArgs> Updated;
        public event EventHandler<CompletedEventArgs> Exit;

        public void Start(Dispatcher dispatcher, params object[] args)
        {
            Debug.Assert(m_thread == null);
            ShouldStop = false;
            m_dispatcher = dispatcher;
            m_thread = new Thread(RunOuter);
            m_thread.Name = GetType().ToString();
            m_thread.Start(args);
        }

        public virtual void Stop()
        {
            if (m_thread != null)
            {
                ShouldStop = true;
                m_thread.Join();
                m_thread = null;
            }
        }

        private ManualResetEvent m_shouldStopEvent = new ManualResetEvent(false);
        protected bool ShouldStop
        {
            get { return m_shouldStopEvent.WaitOne(0); }
            private set
            {
                if (value)
                    m_shouldStopEvent.Set();
                else
                    m_shouldStopEvent.Reset();
            }
        }

        protected WaitHandle StopWaitHandle
        {
            get { return m_shouldStopEvent; }
        }

        private void RunOuter(object args)
        {
            Exception error = null;
            try
            {
                object[] arguments = (object[])args;
                Run(arguments);
            }
            catch (ThreadInterruptedException)
            {
                Log.Trace("Interrupted thread {0}", Thread.CurrentThread.Name);
            }
            catch (ThreadAbortException)
            {
                Log.Trace("Aborted thread {0}", Thread.CurrentThread.Name);
            }
            catch (Exception e)
            {
                Log.Critical("Unexpected error from {0}: {1}", Thread.CurrentThread.Name, e);
                error = e;
                if (Exit != null)
                {
                    //m_dispatcher.BeginInvoke(Exit, DispatcherPriority.Normal, new object[] { this, new CompletedEventArgs(error) });
                }
                else
                {
                    m_dispatcher.BeginInvoke((Action<Exception>)(ShowUnexpectedError), DispatcherPriority.Send, e);
                }
            }
            finally
            {
                if (Exit != null)
                {
                    m_dispatcher.BeginInvoke(Exit, DispatcherPriority.Normal, new object[] { this, new CompletedEventArgs(error) });
                }
            }
        }

        protected void Update(ProgressChangedEventArgs progress)
        {
            m_dispatcher.BeginInvoke(
                    (EventHandler<ProgressChangedEventArgs>)OnUpdated,
                    DispatcherPriority.Normal,
                    new object[] { this, progress }
                );
        }

        protected void SyncUpdate(ProgressChangedEventArgs progress)
        {
            if (Updated != null)
            {
                BlockAndWaitForStopOrResult(
                    (p) =>
                    {
                        m_dispatcher.Invoke(Updated, DispatcherPriority.Normal, new object[] { this, p });
                    },
                    progress,
                    TimeSpan.FromMilliseconds(-1));
            }
        }

        protected bool BlockAndWaitForStopOrResult(Action<object> action, object actionArg, TimeSpan waitTime)
        {
            if (ShouldStop) return false;

            BlockedWorkerThreadArgs threadArgs =
                new BlockedWorkerThreadArgs()
            {
                Action = action,
                ActionArgument = actionArg
            };

            WaitHandle[] handles = new WaitHandle[] { StopWaitHandle, threadArgs.Finished };
            ThreadPool.QueueUserWorkItem(new WaitCallback(BlockedWorkerThread), threadArgs);

            int waitReturn = WaitHandle.WaitAny(handles, waitTime);

            if (waitReturn == 0)
                return false;

            return true;
        }

        internal class BlockedWorkerThreadArgs
        {
            public ManualResetEvent Finished = new ManualResetEvent(false);
            public Action<object> Action = null;
            public object ActionArgument = null;
        }

        protected void BlockedWorkerThread(object argument)
        {
            BlockedWorkerThreadArgs args = null;
            try
            {
                args = (BlockedWorkerThreadArgs)argument;
                args.Action(args.ActionArgument);
            }
            catch (ThreadInterruptedException)
            {
                Log.Trace("Interrupted thread {0}", Thread.CurrentThread.Name);
            }
            catch (ThreadAbortException)
            {
                Log.Trace("Aborted thread {0}", Thread.CurrentThread.Name);
            }
            catch (Exception e)
            {
                Log.Critical("Unexpected error from {0}: {1}", Thread.CurrentThread.Name, e);

                //If an exception is not handled on either a background user interface (UI) thread (a thread with its own Dispatcher) or a background worker thread (a thread without a Dispatcher), 
                //the exception is not forwarded to the main UI thread. Consequently, DispatcherUnhandledException is not raised. In these circumstances, you will need to write code to do the following:
                //1. Handle exceptions on the background thread.
                //2. Dispatch those exceptions to the main UI thread.
                //3. Rethrow them on the main UI thread without handling them to allow DispatcherUnhandledException to be raised.
                //http://msdn.microsoft.com/en-us/library/system.windows.application.dispatcherunhandledexception.aspx

                //Dispatch those exceptions to the main UI thread
                m_dispatcher.BeginInvoke((Action<Exception>)(ShowUnexpectedError), DispatcherPriority.Send, e);
            }
            finally
            {
                if (args != null)
                {
                    args.Finished.Set();
                }
            }
        }

        private void ShowUnexpectedError(Exception e)
        {
            //try
            //{
            //    if (e is SqlException) ExceptionHandler.HandleSqlException((SqlException)e);
            //    else new MessageBox(e).ShowModal();
            //}
            //catch (DirectAuditException ex)
            //{
            //    new MessageBox(MessageBoxType.Error, ex.Message).ShowModal();
            //}
        }

        protected abstract void Run(params object[] args);

        protected void OnUpdated(object sender, ProgressChangedEventArgs e)
        {
            if (Updated != null)
            {
                Updated(sender, e);
            }
        }
    }

    public class CompletedEventArgs : EventArgs
    {
        private Exception m_error;
        public CompletedEventArgs(Exception e)
        {
            m_error = e;
        }

        public Exception Error
        {
            get { return m_error; }
        }
    }
}
